﻿using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Transactions;
using CoreEx.Common.Proxy;
using CoreEx.Database.Client;
using LinFu.IoC.Interfaces;
using LinFu.IoC.Configuration;
using LinFu.IoC;
using System.Data;
using LinFu.Proxy.Interfaces;

namespace CoreEx.Database.Client.Implementation
{
    [Factory(typeof(IDbConnection))]
    public class ConnectionFactory : IFactory<IDbConnection>
    {
        private static IDictionary<Transaction, IDictionary<string, IDbConnection>> _transactionConnections =
            new Dictionary<Transaction, IDictionary<string, IDbConnection>>();
        
        
        #region IFactory Members



        public IDbConnection CreateInstance(IFactoryRequest request)
        {

            //Get the connection info that corresponds to the service name;
            var connectionInfo = request.Container.GetService<DbConnectionInfo>(request.ServiceName);

            var currentTransaction = Transaction.Current;
            if (currentTransaction == null)
                return CreateConnection(request, connectionInfo);


            IDictionary<string, IDbConnection> connectionList;
            IDbConnection connection;


            if (!_transactionConnections.TryGetValue(currentTransaction, out connectionList))
            {
                // We don't have a list for this transaction, so create a new one
                connectionList = new Dictionary<string, IDbConnection>();
                _transactionConnections.Add(currentTransaction, connectionList);

                // We need to know when this previously unknown transaction is completed too
                currentTransaction.TransactionCompleted += OnTransactionCompleted;
            }

            if (!connectionList.TryGetValue(connectionInfo.Name, out connection))
            {
                connection = CreateConnection(request, connectionInfo);
                connectionList.Add(connectionInfo.Name, connection);
            }

            return connection;
        }

        static void OnTransactionCompleted(object sender, TransactionEventArgs e)
        {
            IDictionary<string, IDbConnection> connectionList;

            lock (_transactionConnections)
            {
                if (!_transactionConnections.TryGetValue(e.Transaction, out connectionList))
                {
                    // we don't know about this transaction. odd.
                    return;
                }

                // we know about this transaction - remove it from the mappings
                _transactionConnections.Remove(e.Transaction);
            }

            lock (connectionList)
            {
                // acquiring this lock should not be necessary unless there's a possibility for this event to be fired
                // while the transaction involved in the event is still set as the current transaction for a 
                // different thread.
                foreach (IDbConnection connection in connectionList.Values)
                {
                    connection.Dispose();
                }
            }
        }

        private IDbConnection CreateConnection(IFactoryRequest request, DbConnectionInfo connectionInfo)
        {
            var dbProviderFactory = DbProviderFactories.GetFactory(connectionInfo.ProviderName);

            //Create the connection
            var dbConnection = dbProviderFactory.CreateConnection();
            
            dbConnection.ConnectionString = connectionInfo.ConnectionString;

            var proxyFactory = request.Container.GetService<IProxyFactory>();

            var invokeWrapper = request.Container.GetService<IInvokeWrapper<IDbConnection>>(null,
                                                                                            new object[]
                                                                                                {
                                                                                                    connectionInfo.Name,
                                                                                                    dbConnection
                                                                                                });

            var dbConnectionProxy = proxyFactory.CreateProxy<IDbConnection>(invokeWrapper,
                                                                            typeof (IActualTarget), typeof (INamedConnection));
            dbConnectionProxy.Open();            
            return dbConnectionProxy;
        }

        #endregion
    }
}